package com.xci.core.helper;

import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.hutool.core.lang.ObjectId;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.text.StrSpliter;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.github.promeg.pinyinhelper.Pinyin;
import com.vdurmont.emoji.EmojiParser;
import com.xci.core.annotation.AllowAnonymous;
import com.xci.core.annotation.Authorize;
import com.xci.core.exceptions.AppException;
import com.xci.core.internal.Const;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;
import org.springframework.ui.ModelMap;
import org.springframework.util.DigestUtils;
import org.springframework.util.ObjectUtils;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.function.Function;

/**
 * 快速操作类
 *
 * @author 吕艳阳
 */
public class Helper {
    private static final Snowflake snowflake = new Snowflake(1, 1);
    private static final SecretKeySpec secretKey = new SecretKeySpec(Const.AES_SECRET_KEY.getBytes(StandardCharsets.UTF_8), Const.AES_METHOD);
    private static final IvParameterSpec ivSpec = new IvParameterSpec(Const.AES_Padding_IV.getBytes(StandardCharsets.UTF_8));
    private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false);
    private static Whitelist whitelist;

    /**
     * 清除Html不安全标签
     */
    public static String cleanHtml(String content) {
        if (isBlank(content)) {
            return content;
        }
        if (whitelist == null) {
            whitelist = Whitelist.relaxed();
            //白名单 增加可信标签到白名单
            whitelist.addTags("embed", "object", "param", "span", "div");

            //增加可信属性
            whitelist.addAttributes(":all", "style", "class", "id", "tableName", "src");
            whitelist.addAttributes("object", "width", "height", "classid", "codebase");
            whitelist.addAttributes("param", "tableName", "value");
            whitelist.addAttributes("table", "summary", "width", "cellpadding", "cellspacing", "border", "bordercolor");
            whitelist.addAttributes("embed", "src", "quality", "width", "height", "allowFullScreen",
                    "allowScriptAccess", "flashvars", "tableName", "type", "pluginspage");
        }
        return Jsoup.clean(content, "", whitelist, outputSettings);
    }

    /**
     * 获取所给中文的每个汉字首字母组成首字母字符串
     *
     * @param chinese 汉字字符串
     * @return 首字母字符串
     */
    public static String getSpell(String chinese) {
        return getSpell(chinese, false);
    }

    /**
     * 获取所给中文的每个汉字字母字符串
     *
     * @param chinese 汉字字符串
     * @param isFull  是否全拼
     * @return 首字母字符串
     */
    public static String getSpell(String chinese, boolean isFull) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < chinese.length(); i++) {
            char item = chinese.charAt(i);
            String py = Pinyin.toPinyin(item);
            if (!isFull) {
                stringBuilder.append(py, 0, 1);
            } else {
                stringBuilder.append(py);
            }
        }
        return stringBuilder.toString().toLowerCase();
    }

    /**
     * 检查字符，防止注入绕过
     */
    public static String escapeOrderBySql(String value) {
        if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) {
            return StringUtils.EMPTY;
        }
        return value;
    }

    /**
     * 格式化文本, {} 表示占位符<br>
     * 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
     * 如果想输出 {} 使用 \\转义 { 即可，如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
     * 例：<br>
     * 通常使用：format("this is {} for {}", "a", "b") =》 this is a for b<br>
     * 转义{}： format("this is \\{} for {}", "a", "b") =》 this is \{} for a<br>
     * 转义\： format("this is \\\\{} for {}", "a", "b") =》 this is \a for b<br>
     *
     * @param template 文本模板，被替换的部分用 {} 表示
     * @param params   参数值
     * @return 格式化后的文本
     */
    public static String format(CharSequence template, Object... params) {
        if (null == template) {
            return null;
        }
        if (params == null || params.length == 0 || StrUtil.isBlank(template)) {
            return template.toString();
        }
        return StrFormatter.format(template.toString(), params);
    }

    /**
     * 有序的格式化文本，使用{number}做为占位符<br>
     * 例：<br>
     * 通常使用：format("this is {0} for {1}", "a", "b") =》 this is a for b<br>
     *
     * @param pattern   文本格式
     * @param arguments 参数
     * @return 格式化后的文本
     */
    public static String formatIndexed(CharSequence pattern, Object... arguments) {
        return MessageFormat.format(pattern.toString(), arguments);
    }

    /**
     * 格式化文本，使用 {varName} 占位<br>
     * map = {a: "aValue", b: "bValue"} format("{a} and {b}", map) ---=》 aValue and bValue
     *
     * @param template 文本模板，被替换的部分用 {key} 表示
     * @param map      参数值对
     * @return 格式化后的文本
     */
    public static String format(CharSequence template, Map<?, ?> map) {
        return StrUtil.format(template, map);
    }

    /**
     * 验证 sortDirField by 语法是否符合规范
     */
    public static boolean isValidOrderBySql(String value) {
        return value.matches(Const.SQL_PATTERN);
    }

    public static boolean contains(String source, String sub) {
        return source.toLowerCase().contains(sub.toLowerCase());
    }

    /**
     * 以 conjunction 为分隔符将多个对象转换为字符串
     *
     * @param conjunction 分隔符
     * @param objs        数组
     * @return 连接后的字符串
     * @see ArrayUtil#join(Object, CharSequence)
     */
    public static String join(CharSequence conjunction, Object... objs) {
        return ArrayUtil.join(objs, conjunction);
    }

    /**
     * 切分字符串
     *
     * @param str 被切分的字符串
     * @return 切分后的数组
     */
    public static String[] splitToArray(CharSequence str) {
        return splitToArray(str, ',');
    }

    /**
     * 切分字符串
     *
     * @param str 被切分的字符串
     * @return 切分后的数组
     */
    public static String[] splitToArray(CharSequence str, char separator) {
        if (null == str) {
            return new String[]{};
        }
        return StrSpliter.splitToArray(str.toString(), ',', 0, true, true);
    }

    /**
     * 切分字符串
     *
     * @param str 被切分的字符串
     * @return 切分后的数组
     */
    public static List<String> splitToList(CharSequence str) {
        return StrUtil.split(str, ',', true, true);
    }

    /**
     * 切分字符串
     *
     * @param str 被切分的字符串
     * @return 切分后的数组
     */
    public static List<String> splitToList(CharSequence str, char separator) {
        return StrUtil.split(str, separator, true, true);
    }

    /**
     * 提取html中的文字
     */
    public static String htmlToText(String html) {
        if (isNotBlank(html)) {
            return html.replaceAll("(?s)<[^>]*>(\\s*<[^>]*>)*", " ");
        }
        return "";
    }

    /**
     * An :grinning:awesome :smiley:string &#128516;with a few :wink:emojis!
     * <p>
     * 这种格式的字符转换为emoji表情
     */
    public static String replaceEmoji(String value) {
        return EmojiParser.parseToUnicode(value);
    }

    /**
     * 字符串是否为空白 空白的定义如下： <br>
     * 1、为null <br>
     * 2、为不可见字符（如空格）<br>
     * 3、""<br>
     *
     * @param str 被检测的字符串
     * @return 是否为空
     */
    public static boolean isBlank(CharSequence str) {
        return StrUtil.isBlank(str);
    }

    /**
     * 字符串是否为非空白 空白的定义如下： <br>
     * 1、不为null <br>
     * 2、不为不可见字符（如空格）<br>
     * 3、不为""<br>
     *
     * @param str 被检测的字符串
     * @return 是否为非空
     */
    public static boolean isNotBlank(CharSequence str) {
        return !isBlank(str);
    }

    /**
     * 字符串是否为空，空的定义如下:<br>
     * 1、为null <br>
     * 2、为""<br>
     *
     * @param str 被检测的字符串
     * @return 是否为空
     */
    public static boolean isEmpty(CharSequence str) {
        return str == null || str.length() == 0;
    }

    /**
     * 字符串是否为非空白 空白的定义如下： <br>
     * 1、不为null <br>
     * 2、不为""<br>
     *
     * @param str 被检测的字符串
     * @return 是否为非空
     */
    public static boolean isNotEmpty(CharSequence str) {
        return !isEmpty(str);
    }

    /**
     * 十六进制字符串转为字节数组
     *
     * @param str 字符串
     */
    public static byte[] fromHex(String str) {
        byte[] digest = new byte[str.length() / 2];
        for (int i = 0; i < digest.length; i++) {
            String byteString = str.substring(2 * i, 2 * i + 2);
            int byteValue = Integer.parseInt(byteString, 16);
            digest[i] = (byte) byteValue;
        }
        return digest;
    }

    /**
     * 字节数组转为十六进制字符串
     */
    public static String toHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte value : bytes) {
            String plainText = Integer.toHexString(0xff & value);
            if (plainText.length() < 2) plainText = "0" + plainText;
            hexString.append(plainText);
        }
        return hexString.toString();
    }

    /**
     * 将字节码转换成base64文本
     *
     * @param bytes 字节数组
     */
    public static String toBase64(byte[] bytes) {
        return Base64.getEncoder().encodeToString(bytes);
    }

    /**
     * 将文本转换成base64文本
     *
     * @param content 待转文本
     */
    public static String toBase64Str(String content) {
        return Base64.getEncoder().encodeToString(content.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 将base64转换成字节码
     *
     * @param base64 编码字符串
     */
    public static byte[] fromBase64(String base64) {
        return Base64.getDecoder().decode(base64);
    }

    /**
     * 将base64转换成文本
     *
     * @param base64 编码字符串
     */
    public static String fromBase64Str(String base64) {
        return new String(Base64.getDecoder().decode(base64), StandardCharsets.UTF_8);
    }

    /**
     * 字符串加密
     *
     * @param str 待加密的字符串
     */
    public static String encrypt(String str) {
        if (Helper.isBlank(str)) {
            return Const.EMPTY;
        }
        try {
            Cipher cipher = Cipher.getInstance(Const.AES_INSTANCE);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
            return toBase64(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            throw new AppException("数据加密出现问题");
        }
    }

    /**
     * 字符串解密
     *
     * @param str 待解密的字符串
     */
    public static String decrypt(String str) {
        if (Helper.isBlank(str)) {
            return Const.EMPTY;
        }
        try {
            Cipher cipher = Cipher.getInstance(Const.AES_INSTANCE);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
            return new String(cipher.doFinal(fromBase64(str)), StandardCharsets.UTF_8);
        } catch (Exception e) {
           throw new AppException("数据解密出现问题");
        }
    }


    /**
     * 开始计时监控
     *
     * @return 返回当前时刻的毫秒数
     */
    public static long startWatch() {
        return System.currentTimeMillis();
    }

    /**
     * 停止计时监控
     *
     * @param startTime 开始时间
     * @return 返回执行的毫秒数
     */
    public static long stopWatch(long startTime) {
        return System.currentTimeMillis() - startTime;
    }

    /**
     * 获取exception的详细错误信息。
     */
    public static String getExceptionDetails(Throwable e) {
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw, true));
        return sw.toString();
    }

    public static String getHostIP() {
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return "127.0.0.1";
    }

    public static String getHostName() {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return "未知";
    }

    /**
     * 去除map中值两边空格
     */
    public static void trimMapValue(Map<String, Object> map) {
        for (Map.Entry<String, Object> item : map.entrySet()) {
            Object value = item.getValue();
            if (value instanceof String && !ObjectUtils.isEmpty(value)) {
                item.setValue(value.toString().trim());
            }
        }
    }

    /**
     * 获取验证错误信息
     */
    public static String getValidMsg(String field, String msg) {
        return Helper.format("[{}]{};", field, msg);
    }

    /**
     * 对象属性值比较
     *
     * @param oldBean 原实体
     * @param newBean 新实体
     * @param <T>     实体类型
     * @return 返回修改变化字符串
     */
    public static <T> String contrast(T oldBean, T newBean) {
        // 创建字符串拼接对象
        StringBuilder sb = new StringBuilder();
        // 通过反射获取类的Class对象
        Class clazz = oldBean.getClass();
        // 获取类型及字段属性
        Field[] fields = ReflectUtil.getFields(clazz);
        int i = 1;

        for (Field field : fields) {
            String fileName = field.getName();
            if (fileName.equalsIgnoreCase("serialVersionUID")) {
                continue;
            }
            if (field.isAnnotationPresent(Excel.class)) {
                fileName = field.getAnnotation(Excel.class).name();
            }
            try {
                PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);
                // 获取对应属性值
                Method getMethod = pd.getReadMethod();
                Object o1 = getMethod.invoke(oldBean);
                Object o2 = getMethod.invoke(newBean);
                if (o1 == null || o2 == null) {
                    continue;
                }
                if (!o1.toString().equals(o2.toString())) {
                    sb.append(i).append("、").append(fileName).append(":").append("修改前=>").append(o1).append(
                            ",修改后=>").append(o2).append("\n");
                    i++;
                }
            } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }

    /**
     * 生成普通 Guid
     */
    public static String guid() {
        return UUID.fastUUID().toString(true);
    }

    /**
     * 创建MongoDB ID生成策略实现
     */
    public static String objectId() {
        return ObjectId.next();
    }

    /**
     * 获取新唯一编号（18位数值）
     */
    public static long nextId() {
        return snowflake.nextId();
    }

    /**
     * 获取新唯一编号（18位数值）字符串
     */
    public static String nextIdStr() {
        return String.valueOf(snowflake.nextId());
    }

    /**
     * 更新列表元素
     *
     * @param list      列表对象
     * @param newItem   新元素
     * @param predicate 更新条件
     * @param <E>       对象类型
     * @return 更新成功返回true
     */
    public static <E> boolean updateListItem(List<E> list, E newItem, Function<E, Boolean> predicate) {
        int index = listIndexOf(list, predicate);
        if (index > -1) {
            list.set(index, newItem);
            return true;
        }
        return false;
    }

    /**
     * 根据条件获取元素索引号
     */
    public static <E> int listIndexOf(List<E> list, Function<E, Boolean> predicate) {
        if (list != null && predicate != null) {
            for (int i = 0; i < list.size(); ++i) {
                E item = list.get(i);
                if (predicate.apply(item)) {
                    return i;
                }
            }
        }

        return -1;
    }

    /**
     * 标记新建记录
     *
     * @param map 模型对象
     */
    public static void createMark(ModelMap map) {
        map.put(Const.CREATE_MARK, "1");
    }

    /**
     * 是否新建记录
     */
    public static boolean isCreate() {
        return "1".equals(WebHelper.getRequest().getParameter(Const.CREATE_MARK));
    }

    /**
     * 检测方法是否属于匿名方法
     *
     * @param method 方法对象
     */
    public static boolean IsAllowAnonymous(Method method) {
        if (method.getAnnotation(AllowAnonymous.class) != null || method.getDeclaringClass().getAnnotation(
                AllowAnonymous.class) != null) {
            return true;
        }
        // 类和方法都没有@Authorize,不需要用户认证,直接放行
        Authorize classAuth = method.getDeclaringClass().getAnnotation(Authorize.class);
        Authorize methodAuth = method.getAnnotation(Authorize.class);
        return methodAuth == null && classAuth == null;
    }

    /**
     * @param params 所有的请求参数都会在这里进行排序加密
     * @return 验证签名结果
     */
    public static boolean verifySign(SortedMap<String, String> params) {
        String urlSign = params.get("sign");
        // log.info("Url Sign : {}", urlSign);
        if (StringUtils.isEmpty(urlSign)) {
            return false;
        }
        // 把参数加密  要先去掉 Url 里的 Sign
        params.remove("sign");
        String paramsJsonStr = JsonHelper.serialize(params);
        String paramsSign = DigestUtils.md5DigestAsHex(paramsJsonStr.getBytes()).toUpperCase();

        // log.info("Param Sign : {}", paramsSign);
        return !StringUtils.isEmpty(paramsSign) && urlSign.equals(paramsSign);
    }
}